package com.sdy.auth.biz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sdy.auth.biz.constants.AppConstants;
import com.sdy.auth.biz.model.SysDept;
import com.sdy.auth.biz.model.SysResource;
import com.sdy.auth.biz.model.SysRole;
import com.sdy.auth.biz.model.SysRoleResource;
import com.sdy.auth.biz.model.SysUser;
import com.sdy.auth.biz.model.SysUserApp;
import com.sdy.auth.biz.model.SysUserDept;
import com.sdy.auth.biz.model.SysUserRole;
import com.sdy.auth.biz.model.dto.SysApp;
import com.sdy.auth.biz.model.dto.UserInfo;
import com.sdy.auth.biz.model.vo.LayTreeItem;
import com.sdy.auth.biz.service.AuthService;
import com.sdy.auth.biz.service.SysDeptService;
import com.sdy.auth.biz.service.SysResourceService;
import com.sdy.auth.biz.service.SysRoleResourceService;
import com.sdy.auth.biz.service.SysRoleService;
import com.sdy.auth.biz.service.SysUserAppService;
import com.sdy.auth.biz.service.SysUserDeptService;
import com.sdy.auth.biz.service.SysUserRoleService;
import com.sdy.auth.biz.service.SysUserService;
import com.sdy.common.constant.Constants;
import com.sdy.common.model.BizException;
import com.sdy.common.model.Result;
import com.sdy.common.utils.Assert;
import com.sdy.common.utils.EncodeUtil;
import com.sdy.common.utils.StringUtil;
import com.sdy.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AuthServiceImpl implements AuthService {
    @Autowired
    private SysResourceService sysResourceService;
    @Autowired
    private SysRoleResourceService sysRoleResourceService;
    @Autowired
    private SysUserRoleService sysUserRoleService;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysDeptService sysDeptService;
    @Autowired
    private SysUserDeptService sysUserDeptService;
    @Autowired
    private SysUserAppService sysUserAppService;
    public static final String NO_ACCESS_URL = "/noaccess";
    
    @Override
    public LayTreeItem findAuthTreeByRole(Integer roleId) {
        List<SysResource> resList = sysResourceService.findResourceWithAuthByRoleId(roleId);
        List<LayTreeItem> authList = resList.stream()
                .map(item -> new LayTreeItem()
                        .setId(item.getId())
                        .setChecked(item.getAuthed().equals(1))
                        .setTitle(item.getName())
                        .setSpread(true)
                        .setDisabled(!item.getState().equals(1))
                        .setParentId(item.getParentId()))
                .collect(Collectors.toList());
        List<LayTreeItem> rootResource = new ArrayList<>();
        Map<Integer, LayTreeItem> m = new HashMap<>();
        for (LayTreeItem item : authList) {
            m.put(item.getId(), item);
        }
        for (LayTreeItem item : authList) {
            if (item.getParentId() == null || item.getParentId() <= 0) {
                rootResource.add(item);
            } else {
                LayTreeItem parent = m.get(item.getParentId());
                if (parent == null) {
                    rootResource.add(item);
                    continue;
                }
                List<LayTreeItem> children = parent.getChildren();
                if (children == null) {
                    children = new ArrayList<>();
                }
                children.add(item);
                parent.setChildren(children);
            }
        }
        return new LayTreeItem().setChildren(rootResource);
    }

    @Override
    public void updateAuth(Integer roleId, List<Integer> resIdList) throws BizException {
        Assert.isNull(roleId, "参数错误");
        // 原有资源
        List<SysRoleResource> roleResList = sysRoleResourceService.listByExample(new SysRoleResource().setRoleId(roleId));
        List<Integer> originResIdList = roleResList.stream().map(SysRoleResource::getResourceId).collect(Collectors.toList());
        // 新增资源
        List<Integer> newResIdList = resIdList.stream().filter(item -> !originResIdList.contains(item)).collect(Collectors.toList());
        // 删除资源
        List<Integer> delResIdList = originResIdList.stream().filter(item -> !resIdList.contains(item)).collect(Collectors.toList());

        // 删除多余角色资源
        if (!delResIdList.isEmpty()) {
            sysRoleResourceService.remove(new QueryWrapper<SysRoleResource>().eq("role_id", roleId).in("resource_id", delResIdList));
        }
        // 增加新增角色资源
        if (!newResIdList.isEmpty()) {
            List<SysRoleResource> toSaveList = newResIdList
                    .stream()
                    .map(item -> new SysRoleResource().setRoleId(roleId).setResourceId(item).setCreateTime(new Date()))
                    .collect(Collectors.toList());
//            sysRoleResourceService.saveBatch(toSaveList);
            for (SysRoleResource item : toSaveList) {
                sysRoleResourceService.save(item);
            }
        }
    }

    @Override
    public SysResource findMenu(Integer userId, String appCode) {
        if (StringUtil.isBlank(appCode)) {
            return new SysResource().setChildren(new ArrayList<>());
        }
        SysResource app = sysResourceService.lambdaQuery()
                .eq(SysResource::getType, 1)
                .eq(SysResource::getState, 1)
                .eq(SysResource::getCode, appCode).one();
        if (app == null) {
            return new SysResource().setChildren(new ArrayList<>());
        }
        List<SysResource> rootResource = new ArrayList<>();
        // 用户拥有的角色
        List<Integer> roles = findRoleByUser(userId, true)
                .stream()
                .map(SysRole::getId)
                .collect(Collectors.toList());
        // 角色拥有的资源
        List<Integer> resourceIdList = findResourceByRoles(roles);

        // 检查是否管理员权限
        boolean admin = false;
        List<Integer> adminRoleList = findAdminRole();
        if (!adminRoleList.isEmpty()) {
            for (Integer item : adminRoleList) {
                if (roles.contains(item)) {
                    admin = true;
                    break;
                }
            }
        }
        LambdaQueryWrapper<SysResource> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysResource::getType, 2);
        if (!admin) {
            resourceIdList.add(-1);
            wrapper.and(wp -> wp.in(SysResource::getId, resourceIdList).eq(SysResource::getState, 1)
                    .or().eq(SysResource::getAuthorization, 2).eq(SysResource::getState, 1));
        } else {
            wrapper.eq(SysResource::getState, 1);
        }
        wrapper.orderByAsc(SysResource::getSort);
        List<SysResource> all = sysResourceService.list(wrapper);
        all.add(app.setChildren(new ArrayList<>()));
        Map<Integer, SysResource> m = new HashMap<>();
        for (SysResource item : all) {
            m.put(item.getId(), item);
        }
        for (SysResource item : all) {
            if (item.getParentId() == null || item.getParentId() <= 0) {
                rootResource.add(item);
            } else {
                SysResource parent = m.get(item.getParentId());
                if (parent != null) {
                    List<SysResource> children = parent.getChildren();
                    if (children == null) {
                        children = new ArrayList<>();
                    }
                    children.add(item);
                    parent.setChildren(children);
                }
            }
        }
        SysResource result = new SysResource().setChildren(rootResource);
        setTopId(result, 0);
        result.setChildren(rootResource.stream().filter(item -> item.getId().equals(app.getId())).collect(Collectors.toList()));
        return result;
    }

    private void setTopId(SysResource s, Integer topId) {
        if (s.getParentId() != null && s.getParentId().equals(-1)) {
            s.setRootId(s.getId());
        } else {
            s.setRootId(topId);
        }
        if (s.getChildren() != null && s.getChildren().size() > 0) {
            for (SysResource item : s.getChildren()) {
                setTopId(item, s.getRootId());
            }
        }
    }

    @Override
    public List<SysRole> findRoleByUser(Integer userId, Boolean valid) {
        if (userId == null) {
            return new ArrayList<>();
        }
        List<SysUserRole> rolelist = sysUserRoleService.lambdaQuery().eq(SysUserRole::getUserId, userId).list();
        if (rolelist.isEmpty()) {
            return new ArrayList<>();
        }
        List<Integer> roleIdList = rolelist.stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
        return sysRoleService.lambdaQuery().eq(valid, SysRole::getState, 1).in(SysRole::getId, roleIdList).list();
    }

    @Override
    public List<Integer> findResourceByRoles(List<Integer> roleIds) {
        if (roleIds.isEmpty()) {
            return new ArrayList<>();
        }
        List<SysRoleResource> roleResourceList = sysRoleResourceService.lambdaQuery().in(SysRoleResource::getRoleId, roleIds).list();
        return roleResourceList.stream().map(SysRoleResource::getResourceId).collect(Collectors.toList());
    }
    
    @Override
    public List<Integer> findAdminRole() {
        return sysRoleService.lambdaQuery()
                .eq(SysRole::getAdmin, 1)
                .eq(SysRole::getState, 1)
                .list()
                .stream()
                .map(SysRole::getId)
                .collect(Collectors.toList());
    }

    @Override
    public Result<UserInfo> login(String username, String password, String token, String ip) {
        if (!checkPwdError(username)) {
            return new Result<>(null, "密码错误次数过多，请在" + Constants.LOGIN_RETRY_MINUTES + "分钟后重试", -1, false);
        }
        SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().eq("login_name", username));
        if (sysUser == null) {
            return new Result<>(null, "用户不存在", -2, false);
        }
        if (sysUser.getState() == null || !sysUser.getState().equals(1)) {
            return new Result<>(null, "用户不可用", -5, false);
        }
        Date now = new Date();
        if (sysUser.getExpireTime() != null && sysUser.getExpireTime().before(now)) {
            return new Result<>(null, "用户已过期", -6, false);
        }
        if (sysUser.getEffectTime() == null || sysUser.getEffectTime().after(now)) {
            return new Result<>(null, "用户未生效", -7, false);
        }
        if (StringUtil.isNotBlank(sysUser.getAllowIp()) && !Arrays.asList(sysUser.getAllowIp().split(",")).contains(ip)) {
            return new Result<>(null, "不允许该IP[" + ip + "]登录", -8, false);
        }
        String encode = EncodeUtil.sha256(sysUser.getPassword() + token);
        if (!encode.equals(password)) {
            long errTimes = addPwdErrorTimes(username);
            long restTimes = Constants.LOGIN_MAX_ERROR_TIMES - errTimes;
            if (restTimes > 0) {
                return new Result<>(null, "密码错误，还剩" + restTimes + "次机会", -1, false);
            } else {
                return new Result<>(null, "密码错误，请在" + Constants.LOGIN_RETRY_MINUTES + "分钟后重试", -1, false);
            }
        }
        redisService.del(Constants.PASSWORD_ERROR_REDIS + "_" + username);
        // 记录登录信息
        SysUser vo = new SysUser();
        vo.setId(sysUser.getId());
        vo.setLastLoginTime(new Date());
        vo.setLastLoginIp(ip);
        sysUserService.updateById(vo);
        return new Result<>((UserInfo) new UserInfo()
                .setUserId(sysUser.getId())
                .setUserName(sysUser.getName())
                .setSub(sysUser.getId().toString()), "登录成功", 0, true);
    }

    @Override
    public Result<UserInfo> login(String username, String appid, String key, String timestamp, String ip) {
        long ts = System.currentTimeMillis();
        if (Math.abs(new Long(timestamp) - ts) > 1000 * 20*60L) {
            return new Result<>(null, "超时失效", -4, false);
        }
        SysApp app = AppConstants.appMap.get(appid);
        if (app == null) {
            return new Result<>(null, "appid不存在", -1, false);
        }
        SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().eq("login_name", username));
        if (sysUser == null) {
            return new Result<>(null, "用户不存在", -2, false);
        }
        if (sysUser.getState() == null || !sysUser.getState().equals(1)) {
            return new Result<>(null, "用户不可用", -5, false);
        }
        Date now = new Date();
        if (sysUser.getExpireTime() != null && sysUser.getExpireTime().before(now)) {
            return new Result<>(null, "用户已过期", -6, false);
        }
        if (sysUser.getEffectTime() == null || sysUser.getEffectTime().after(now)) {
            return new Result<>(null, "用户未生效", -7, false);
        }
        if (StringUtil.isNotBlank(sysUser.getAllowIp()) && !Arrays.asList(sysUser.getAllowIp().split(",")).contains(ip)) {
            return new Result<>(null, "不允许该IP[" + ip + "]登录", -8, false);
        }
        String en = EncodeUtil.sha256((sysUser.getPassword() + EncodeUtil.sha256(app.getAppkey()) + timestamp));
        if (!en.equals(key)) {
            return new Result<>(null, "key错误", -3, false);
        }
        // 记录登录信息
        SysUser vo = new SysUser();
        vo.setId(sysUser.getId());
        vo.setLastLoginTime(new Date());
        vo.setLastLoginIp(ip);
        sysUserService.updateById(vo);
        return new Result<>((UserInfo) new UserInfo()
                .setUserId(sysUser.getId())
                .setUserName(sysUser.getName())
                .setSub(sysUser.getId().toString()), "登录成功", 0, true);
    }

    @Override
    public Result<Collection> getManagerUserList(Integer userId) {
        if (userId == null) {
            return new Result<>(null, "用户未登录", -101, false);
        }
        SysUser user = sysUserService.getById(userId);
        List<SysUserRole> sysUserRoleList = sysUserRoleService.listByExample(new SysUserRole().setUserId(userId));
        if (sysUserRoleList.isEmpty()) {
            return new Result<>(null, "用户无角色", -102, false);
        }
        Collection<SysRole> roleList = sysRoleService.listByIds(sysUserRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet()));
        if (roleList.isEmpty()) {
            return new Result<>(null, "用户无角色", -102, false);
        }
        long admin = roleList.stream().filter(item -> item.getAdmin() != null && item.getAdmin().equals(1)).count();
        if (admin <= 0) {
            return new Result<>(null, "用户无管理员权限", -103, false);
        }
        // 用户部门
        List<SysUserDept> rootUserDeptList = sysUserDeptService.listByExample(new SysUserDept().setUserId(userId));
        if (rootUserDeptList.isEmpty()) {
            return new Result<>(null, "用户无部门数据", -104, false);
        }
        // 递归部门id
        List<Integer> allDeptList = new ArrayList<>();
        Queue<SysDept> queue = new LinkedList<>();
        Collection<SysDept> rootDeptList = sysDeptService.listByIds(rootUserDeptList.stream().map(SysUserDept::getDeptId).collect(Collectors.toSet()));
        rootDeptList.forEach(queue::offer);
        SysDept tmp;
        Set<Integer> idSet = new HashSet<>();
        int maxCnt = 100000;
        while ((tmp = queue.poll()) != null && --maxCnt > 0) {
            allDeptList.add(tmp.getId());
            if (idSet.contains(tmp.getId())) {
                return new Result<>(null, "项目间存在循环引用", -999, false);
            }
            idSet.add(tmp.getId());
            List<SysDept> children = sysDeptService.listByExample(new SysDept().setParentId(tmp.getId()));
            if (!children.isEmpty()) {
                tmp.setChildren(children);
                children.forEach(queue::offer);
            }
        }

        if (allDeptList.isEmpty()) {
            return new Result<>(null, "用户无部门数据", -104, false);
        }
        // 查询部门下用户
        List<SysUserDept> allUserDeptList = sysUserDeptService.list(new QueryWrapper<SysUserDept>().in("dept_id", allDeptList).eq("admin", 2));
        if (allUserDeptList.isEmpty()) {
            return new Result<>(new ArrayList(), "", 0, true);
        }
        Collection<SysUser> subuserList = sysUserService.list(new QueryWrapper<SysUser>()
                .in("id", allUserDeptList.stream().map(SysUserDept::getUserId).collect(Collectors.toList()))
                .select("id", "login_name"));
        // 包装结果
        Collection<Map> resultList = new ArrayList<>();
        subuserList.forEach(item -> {
            Map<String, Object> m = new HashMap<>();
            m.put("id", item.getId());
            m.put("loginName", item.getLoginName());
            resultList.add(m);
        });
        return new Result<>(resultList, "", 0, true);
    }

    @Override
    public Boolean checkAuthorization(Integer userId, String path, String appCode) {
        if (NO_ACCESS_URL.equals(path)) {
            return true;
        }
        boolean appRes = false;
        SysResource app = sysResourceService.findAppByCode(appCode);
        if (app == null) {
            return true;
        }
        List<String> regxList = splitUrlToRegx(path);
        SysResource resource = findResourceByPath(path, app.getId());
        if (resource == null) {
            for (String s : regxList) {
                resource = findResourceByPath(s, app.getId());
                if (resource != null) {
                    break;
                }
            }
            if (resource == null) {
                resource = app;
                appRes = true;
            }
        }
        if (resource.getAuthentication().equals(2)) {
            return true;
        }
        if (resource.getAuthorization().equals(2) && userId != null) {
            return true;
        }
        if (userId != null) {
            List<Integer> adminRoleList = findAdminRole();
            List<Integer> userRoleIdList = findRoleByUser(userId, true).stream().map(SysRole::getId).collect(Collectors.toList());
            adminRoleList.retainAll(userRoleIdList);
            if (!adminRoleList.isEmpty()) {
                return true;
            } else {
///                SysResource userApp = findUserApp(userId, resource.getId());
///                // 判断有无系统登录权限
///                if (app.getAuthorization().equals(1) && userApp == null) {
///                    return false;
///                }
///                if (appRes) {
///                    return true;
///                } else {
///                    List<Integer> roleIdList = findRoleByResourceId(resource.getId());
///                    roleIdList.retainAll(userRoleIdList);
///                    return !roleIdList.isEmpty();
///                }
                List<Integer> roleIdList = findRoleByResourceId(resource.getId());
                roleIdList.retainAll(userRoleIdList);
                return !roleIdList.isEmpty();
            }
        }
        return false;
    }
//
//    @Override
//    public List deptManagerTree(Integer userId) throws BizException {
//        List<SysUserDept> userDeptList = sysUserDeptService.list(new LambdaQueryWrapper<SysUserDept>()
//                .eq(SysUserDept::getUserId, userId)
//                .eq(SysUserDept::getAdmin, 1)
//        );
//        if (userDeptList.isEmpty()) {
//            return new ArrayList();
//        }
//        Collection<SysDept> deptList = sysDeptService.listByIds(userDeptList
//                .stream()
//                .map(SysUserDept::getDeptId)
//                .collect(Collectors.toList()));
//
//        List<Integer> allDeptList = new ArrayList<>();
//        Queue<SysDept> queue = new LinkedList<>();
//        deptList.forEach(queue::offer);
//        SysDept tmp;
//        Set<Integer> idSet = new HashSet<>();
//        int maxCnt = 100000;
//        while ((tmp = queue.poll()) != null && --maxCnt > 0) {
//            allDeptList.add(tmp.getId());
//            if (idSet.contains(tmp.getId())) {
//                throw new BizException("项目间存在循环引用");
//            }
//            idSet.add(tmp.getId());
//            List<SysDept> children = sysDeptService.listByExample(new SysDept().setParentId(tmp.getId()));
//            if (!children.isEmpty()) {
//                tmp.setChildren(children);
//                children.forEach(queue::offer);
//            }
//        }
//
//        if (allDeptList.isEmpty()) {
//            return new Result<>(null, "用户无部门数据", -104, false);
//        }
//    }

    /**
     * 查询用户拥有的应用
     */
    private SysResource findUserApp(Integer userId, Integer resId) {
        if (userId == null) {
            return null;
        }
        if (resId == null) {
            return null;
        }
        List<SysUserApp> list = sysUserAppService.lambdaQuery().eq(SysUserApp::getUserId, userId).eq(SysUserApp::getAppResId, resId).list();
        if (list.isEmpty()) {
            return null;
        }
        return sysResourceService.getById(list.get(0).getAppResId());
    }

    /**
     * 查询用户拥有的应用
     */
    private List<SysResource> findUserAppList(Integer userId) {
        if (userId == null) {
            return new ArrayList<>();
        }
        List<SysUserApp> list = sysUserAppService.list(new QueryWrapper<SysUserApp>().eq("user_id", userId));
        if (list.isEmpty()) {
            return new ArrayList<>();
        }
        Collection<SysResource> resList = sysResourceService.listByIds(list.stream().map(SysUserApp::getAppResId).collect(Collectors.toList()));
        return new ArrayList<>(resList);
    }

    private List<Integer> findRoleByResourceId(Integer resourceId) {
        List<SysRoleResource> roleResList = sysRoleResourceService.findRoleByResourceId(resourceId);
        return roleResList.stream().map(SysRoleResource::getRoleId).collect(Collectors.toList());
    }
    
    private List<String> splitUrlToRegx(String url) {
        String[] regx = url.split("/");
        List<String> regxPathList = new ArrayList<>();
        StringBuilder tmps = new StringBuilder();
        for (String s : regx) {
            if (StringUtil.isNotBlank(s)) {
                tmps.append("/").append(s);
                regxPathList.add(tmps.toString().concat("/*"));
            }
        }
        return regxPathList;
    }

    private SysResource findResourceByPath(String path, Integer appResId) {
        List<SysResource> candidateResList = sysResourceService.lambdaQuery()
                .eq(SysResource::getRootId, appResId)
                .like(SysResource::getUrl, path)
                .list();
        for (SysResource candidateRes : candidateResList) {
            String[] ss = candidateRes.getUrl().split(";");
            for (String s : ss) {
                if (s.equals(path)) {
                    return candidateRes;
                }
            }
        }
        return null;
    }

    private boolean checkPwdError(String username) {
        Integer times = redisService.get(Constants.PASSWORD_ERROR_REDIS + "_" + username, Integer.class);
        return times == null || times < Constants.LOGIN_MAX_ERROR_TIMES;
    }

    private long addPwdErrorTimes(String username) {
        Long t = redisService.incr(Constants.PASSWORD_ERROR_REDIS + "_" + username);
        redisService.expire(Constants.PASSWORD_ERROR_REDIS + "_" + username, Constants.LOGIN_RETRY_MINUTES * 60);
        return t;
    }
}
